﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Common;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;

namespace Moon.Sql
{
    internal class DataMapper
    {
        public static Dictionary<Type, DbType> TypeMap;

        #region
        static DataMapper()
        {
            TypeMap = new Dictionary<Type, DbType>(37)
            {
                [typeof(byte)] = DbType.Byte,
                [typeof(sbyte)] = DbType.SByte,
                [typeof(short)] = DbType.Int16,
                [typeof(ushort)] = DbType.UInt16,
                [typeof(int)] = DbType.Int32,
                [typeof(uint)] = DbType.UInt32,
                [typeof(long)] = DbType.Int64,
                [typeof(ulong)] = DbType.UInt64,
                [typeof(float)] = DbType.Single,
                [typeof(double)] = DbType.Double,
                [typeof(decimal)] = DbType.Decimal,
                [typeof(bool)] = DbType.Boolean,
                //#2022.02.24 20:20
                //[typeof(string)] = DbType.String,
                [typeof(string)] = DbType.AnsiString,
                [typeof(char)] = DbType.StringFixedLength,
                [typeof(Guid)] = DbType.Guid,
                [typeof(DateTime)] = DbType.DateTime,
                [typeof(DateTimeOffset)] = DbType.DateTimeOffset,
                [typeof(TimeSpan)] = DbType.Time,
                [typeof(byte[])] = DbType.Binary,
                [typeof(byte?)] = DbType.Byte,
                [typeof(sbyte?)] = DbType.SByte,
                [typeof(short?)] = DbType.Int16,
                [typeof(ushort?)] = DbType.UInt16,
                [typeof(int?)] = DbType.Int32,
                [typeof(uint?)] = DbType.UInt32,
                [typeof(long?)] = DbType.Int64,
                [typeof(ulong?)] = DbType.UInt64,
                [typeof(float?)] = DbType.Single,
                [typeof(double?)] = DbType.Double,
                [typeof(decimal?)] = DbType.Decimal,
                [typeof(bool?)] = DbType.Boolean,
                [typeof(char?)] = DbType.StringFixedLength,
                [typeof(Guid?)] = DbType.Guid,
                [typeof(DateTime?)] = DbType.DateTime,
                [typeof(DateTimeOffset?)] = DbType.DateTimeOffset,
                [typeof(TimeSpan?)] = DbType.Time,
                [typeof(object)] = DbType.Object
            };
        }
        #endregion

        public static T Parse<T>(object value)
        {
            if (value is null || value is DBNull) return default(T);
            if (value is T t) return t;
            var type = typeof(T);
            type = Nullable.GetUnderlyingType(type) ?? type;
            if (type.IsEnum)
            {
                if (value is float || value is double || value is decimal)
                {
                    value = Convert.ChangeType(value, Enum.GetUnderlyingType(type), CultureInfo.InvariantCulture);
                }
                return (T)Enum.ToObject(type, value);
            }
            return (T)Convert.ChangeType(value, type, CultureInfo.InvariantCulture);
        }

        public static T GetValue<T>(Type effectiveType, object val)
        {
            if (val is T tVal)
            {
                return tVal;
            }
            else if (val == null && (!effectiveType.IsValueType || Nullable.GetUnderlyingType(effectiveType) != null))
            {
                return default(T);
            }
            else
            {
                var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
                return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture);
            }
        }

        public static T GetDataReaderFirst<T>(IDataReader reader)
        {
            if (reader.Read())
            {
                var effectiveType = typeof(T);
                var func = DataMapperFactory.Builder(reader, effectiveType);
                object val = func(reader);
                return GetValue<T>(effectiveType, val);
            }
            return default(T);
        }

        public static async Task<T> GetDataReaderFirstAsync<T>(IDataReader reader)
        {
            if (await (reader as DbDataReader).ReadAsync().ConfigureAwait(false))
            {
                var effectiveType = typeof(T);
                var func = DataMapperFactory.Builder(reader, effectiveType);
                object val = func(reader);
                return GetValue<T>(effectiveType, val);
            }
            return default(T);
        }

        public static List<T> GetDataReader<T>(IDataReader reader)
        {
            List<T> list = new List<T>();
            var effectiveType = typeof(T);
            var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
            var deserializer = DataMapperFactory.Builder(reader, effectiveType);
            while (reader.Read())
            {
                object val = deserializer(reader);
                if (val == null || val is T)
                    list.Add((T)val);
                else
                    list.Add((T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture));
            }
            return list;
        }

        public static async Task<List<T>> GetDataReaderAsync<T>(IDataReader reader)
        {
            List<T> list = new List<T>();
            var effectiveType = typeof(T);
            var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
            var deserializer = DataMapperFactory.Builder(reader, effectiveType);
            while (await (reader as DbDataReader).ReadAsync().ConfigureAwait(false))
            {
                object val = deserializer(reader);
                if (val == null || val is T)
                    list.Add((T)val);
                else
                    list.Add((T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture));
            }
            return list;
        }

        public static object LoadParameterValue(object value)
        {
            if (value == null) return DBNull.Value;
            if (value is Enum)
            {
                TypeCode typeCode = value is IConvertible convertible
                    ? convertible.GetTypeCode()
                    : Type.GetTypeCode(Enum.GetUnderlyingType(value.GetType()));

                switch (typeCode)
                {
                    case TypeCode.Byte: return (byte)value;
                    case TypeCode.SByte: return (sbyte)value;
                    case TypeCode.Int16: return (short)value;
                    case TypeCode.Int32: return (int)value;
                    case TypeCode.Int64: return (long)value;
                    case TypeCode.UInt16: return (ushort)value;
                    case TypeCode.UInt32: return (uint)value;
                    case TypeCode.UInt64: return (ulong)value;
                }
            }
            Type type = value.GetType();
            if (type == typeof(DateTime) || type == typeof(Nullable<DateTime>))
            {
                if (Convert.ToDateTime(value).Year == 1) return DBNull.Value;
            }
            return value;
        }

        public static DbType LoadDbType(Type type)
        {
            var nullUnderlyingType = Nullable.GetUnderlyingType(type);
            if (nullUnderlyingType != null) type = nullUnderlyingType;
            if (type.IsEnum && !TypeMap.ContainsKey(type))
            {
                type = Enum.GetUnderlyingType(type);
            }
            if (TypeMap.TryGetValue(type, out DbType dbType))
            {
                return dbType;
            }
            if (type.FullName == "System.Data.Linq.Binary")
            {
                return DbType.Binary;
            }
            return DbType.String;
        }
    }
}
